package com.xx.sg.sso;

import com.dtflys.forest.Forest;
import com.xx.sg.util.AjaxJson;
import com.xx.sg.util.JsonUtil;
import com.xx.sg.util.SaCheckTicketResult;
import jakarta.servlet.http.HttpServletRequest;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Random;

/**
 * 封装一些 sso 共用方法
 *
 * @author click33
 * @since 2022-4-30
 */
public class SsoRequestUtil {

	/**
	 * SSO-Server端主机地址
	 */
	public static String serverUrl = "http://10.62.0.1:9000";
//	public static String serverUrl = "http://10.62.0.33:9000";


	/**
	 * SSO-Server端 统一认证地址
	 */
	public static String authUrl = serverUrl + "/sso/auth";

	/**
	 * SSO-Server端 ticket校验地址
	 */
	public static String checkTicketUrl = serverUrl + "/sso/checkTicket";

	/**
	 * 单点注销地址
	 */
	public static String sloUrl = serverUrl + "/sso/signout";

	/**
	 * SSO-Server端 查询userinfo地址
	 */
	public static String userinfoUrl = serverUrl + "/sso/userinfo";

	/**
	 * 是否打开单点注销功能
	 */
	public static boolean isSlo = true;

	/**
	 * 当前 client 的标识，可为 null
	 *  sync_user
	 *  Sg2023@!!##
	 */
	public static String client = "client-yjzh";

	/**
	 * 接口调用秘钥
	 */
	public static String secretKey = "SSP-C3-NOSDK-YQfyZtAmDbYHTBaHPSx3GZeX7x2ip7ik";


	// -------------------------- 封装方法

	/**
	 * 拼接 sso 授权地址
	 * @return /
	 */
	public static String buildServerAuthUrl(String clientLoginUrl) {
		String serverAuthUrl = authUrl;
		if(isNotEmpty(client)) {
			serverAuthUrl = joinParam(serverAuthUrl, "client=" + client);
		}
		serverAuthUrl = joinParam(serverAuthUrl, "redirect=" + clientLoginUrl);
		return serverAuthUrl;
	}

	public static SysUser getSysUser(String ticket,HttpServletRequest request) {
		String checkUrl = checkTicketUrl + "?ticket=" + ticket;
		if(isNotEmpty(client)) {
			checkUrl += "&client=" + client;
		}
		String ssoLogoutCall = null;
		String timestamp = String.valueOf(System.currentTimeMillis());	// 时间戳
		String nonce = getRandomString(32);		// 随机字符串
		String sign = getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce);	// 参数签名
		checkUrl +=
				"&timestamp=" + timestamp +
						"&nonce=" + nonce +
						"&sign=" + sign;
		// 发起请求
		AjaxJson result = request(checkUrl);

		// 200 代表校验成功
		if(result.getCode() == 200 && isNotEmpty(result.getData())) {
			// 登录上
			System.out.println(result);
			Object loginId = result.getData();
			getUserInfo(loginId);
			return getUserInfo(loginId);
		} else {
			// 将 sso-server 回应的消息作为异常抛出
			throw new RuntimeException(result.getMsg());
		}
	}

	/**
	 * 校验 ticket，返回 userId
	 * @param ticket ticket 码
	 * @param request 请求对象
	 * @param currPath 当前接口的访问path
	 * @return /
	 */
	public static SaCheckTicketResult checkTicket(String ticket, HttpServletRequest request, String currPath) {
		// 构建校验 ticket 的地址
		String checkUrl = checkTicketUrl + "?ticket=" + ticket;
		if(isNotEmpty(client)) {
			checkUrl += "&client=" + client;
		}
		String ssoLogoutCall = null;
		if(isSlo) {
			ssoLogoutCall = request.getRequestURL().toString().replace(currPath, "/sso/logoutCall");
			checkUrl += "&ssoLogoutCall=" + ssoLogoutCall;
		}
		String timestamp = String.valueOf(System.currentTimeMillis());	// 时间戳
		String nonce = getRandomString(32);		// 随机字符串
		String sign = getSignByTicket(ticket, ssoLogoutCall, timestamp, nonce);	// 参数签名
		checkUrl +=
				"&timestamp=" + timestamp +
				"&nonce=" + nonce +
				"&sign=" + sign;
		// 发起请求
		AjaxJson result = request(checkUrl);

		// 200 代表校验成功
		if(result.getCode() == 200 && isNotEmpty(result.getData())) {
			// 登录上
			System.out.println(result);
			Object loginId = result.getData();
			getUserInfo(loginId);
			long remainSessionTimeout = Long.parseLong(String.valueOf(result.get("remainSessionTimeout")));
			return new SaCheckTicketResult(loginId, remainSessionTimeout, result);
		} else {
			// 将 sso-server 回应的消息作为异常抛出
			throw new RuntimeException(result.getMsg());
		}
	}

	/**
	 * 获取指定用户id的详细资料  (调用此接口的前提是 sso-server 端开放了 /sso/userinfo 路由)
	 */
	public static SysUser getUserInfo(Object loginId) {

        // 组织 url 参数
		String timestamp = String.valueOf(System.currentTimeMillis());	// 时间戳
		String nonce = getRandomString(32);		// 随机字符串
		String sign = getSign(loginId, timestamp, nonce);	// 参数签名

		// 请求
        String url = userinfoUrl +
        		"?loginId=" + loginId +
        		"&timestamp=" + timestamp +
        		"&nonce=" + nonce +
        		"&sign=" + sign;
		if(isNotEmpty(client)) {
			url += "&client=" + client;
		}
        AjaxJson result = request(url);

        // 如果返回值的 code 不是200，代表请求失败
        if(result.getCode() == null || result.getCode() != 200) {
        	throw new RuntimeException(result.getMsg());
        }

        // 解析出 user
        SysUser user = JsonUtil.parseObjectToModel(result.getData(), SysUser.class);
		System.out.println("user:" + user);
        return user;
	}


	// -------------------------- 工具方法

	/**
	 * 发出请求，并返回 SaResult 结果
	 * @param url 请求地址
	 * @return 返回的结果
	 */
	public static AjaxJson request(String url) {
		System.out.println("------ 发起请求：" + url);
		Map<String, Object> map = Forest.post(url).executeAsMap();
		System.out.println("------ 请求结果：" + JsonUtil.toJsonString(map));
		return new AjaxJson(map);
	}

	/**
	 * 根据参数计算签名
	 * @param loginId 账号id
	 * @param timestamp 当前时间戳，13位
	 * @param nonce 随机字符串
	 * @return 签名
	 */
	public static String getSign(Object loginId, String timestamp, String nonce) {
		String fullStr = "";
		if(isNotEmpty(client)) {
			fullStr += "client=" + client + "&";
		}
		fullStr += "loginId=" + loginId + "&nonce=" + nonce + "&timestamp=" + timestamp + "&key=" + secretKey;

		System.out.println("------ 签名原文：" + fullStr);
		return md5(fullStr);
	}

	// 单点注销回调时构建签名
	public static String getSignByLogoutCall(Object loginId, String autoLogout, String timestamp, String nonce) {
		String fullStr = "autoLogout=" + autoLogout;
		if (isNotEmpty(client)) {
			fullStr += "&client=" + client;
		}
		fullStr += "&loginId=" + loginId;
		fullStr += "&nonce=" + nonce;
		fullStr += "&timestamp=" + timestamp;
		fullStr += "&key=" + secretKey;

		System.out.println("------ 签名原文：" + fullStr);
		return md5(fullStr);
	}
	// 校验ticket 时构建签名
	public static String getSignByTicket(String ticket, String ssoLogoutCall, String timestamp, String nonce) {
		String fullStr = "";
		if(isNotEmpty(client)) {
			fullStr += "client=" + client + "&";
		}
		fullStr += "nonce=" + nonce;
		if(isNotEmpty(ssoLogoutCall)) {
			fullStr += "&ssoLogoutCall=" + ssoLogoutCall;
		}
		fullStr += "&ticket=" + ticket;
		fullStr += "&timestamp=" + timestamp;
		fullStr += "&key=" + secretKey;

		System.out.println("------ 签名原文：" + fullStr);
		return md5(fullStr);
	}


	/**
	 * 指定元素是否为null或者空字符串
	 * @param str 指定元素
	 * @return 是否为null或者空字符串
	 */
	public static boolean isEmpty(Object str) {
		return str == null || "".equals(str);
	}

	/**
	 * 指定元素是否不为null或者空字符串
	 * @param str 指定元素
	 * @return 是否为null或者空字符串
	 */
	public static boolean isNotEmpty(Object str) {
		return !isEmpty(str);
	}

	/**
	 * md5加密
	 * @param str 指定字符串
	 * @return 加密后的字符串
	 */
	public static String md5(String str) {
		str = (str == null ? "" : str);
		char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		try {
			byte[] btInput = str.getBytes();
			MessageDigest mdInst = MessageDigest.getInstance("MD5");
			mdInst.update(btInput);
			byte[] md = mdInst.digest();
			int j = md.length;
			char[] strA = new char[j * 2];
			int k = 0;
			for (byte byte0 : md) {
				strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
				strA[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(strA);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 生成指定长度的随机字符串
	 *
	 * @param length 字符串的长度
	 * @return 一个随机字符串
	 */
	public static String getRandomString(int length) {
		String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(62);
			sb.append(str.charAt(number));
		}
		return sb.toString();
	}

	/**
	 * URL编码
	 * @param url see note
	 * @return see note
	 */
	public static String encodeUrl(String url) {
		try {
			return URLEncoder.encode(url, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 在url上拼接上kv参数并返回
	 * @param url url
	 * @param parameStr 参数, 例如 id=1001
	 * @return 拼接后的url字符串
	 */
	public static String joinParam(String url, String parameStr) {
		// 如果参数为空, 直接返回
		if(parameStr == null || parameStr.length() == 0) {
			return url;
		}
		if(url == null) {
			url = "";
		}
		int index = url.lastIndexOf('?');
		// ? 不存在
		if(index == -1) {
			return url + '?' + parameStr;
		}
		// ? 是最后一位
		if(index == url.length() - 1) {
			return url + parameStr;
		}
		// ? 是其中一位
		if(index > -1 && index < url.length() - 1) {
			String separatorChar = "&";
			// 如果最后一位是 不是&, 且 parameStr 第一位不是 &, 就增送一个 &
			if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) {
				return url + separatorChar + parameStr;
			} else {
				return url + parameStr;
			}
		}
		// 正常情况下, 代码不可能执行到此
		return url;
	}
}
